
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Xml;
using System.Text.RegularExpressions;
using System.Threading;


using Swaf.Config;
using Swaf.Container;


namespace Swaf
{
    /// <summary> Manages a map of unique names and regular expressions to fully qualified .NET class names and provides
    /// a factory method (<c>createFrom</c>) to create instances of the class.</summary>
    /// <remarks>ClassFactory is an essential class that is used by many different parts of the framework.  It provides a 
    /// single implementation of the class factory design pattern that can be used for any set of .NET classes.  No
    /// special requirements exist for the classes defined in the ClassFactory, other then needing to implement:
    /// <list>
    /// <item>Public constructor with parameters matching those being passed in to a call to <c>create</c>.</item>
    /// <item>A public static method called parse that takes a string and calls to <c>create</c> have only
    /// a string being passed in.</item>
    /// <item>A sample object is passed in that implements the <c>ICloneable</c> interface.  When calling <c>create</c>
    /// with no parameters, the <c>ICloneable.Clone</c> method is called on the sample passed in to <c>add</c>.
    /// When using this option, the class factory is actually implementing the <term>Prototype design pattern</term>.</item>
    /// </list>
    /// <p>Use the <c>add</c> method for each name or regular expression pattern to be used to create objects
    /// from the factory. Either the direct <c>ClassFactory</c> class can be used to create instances, or a 
    /// create only factory can be used by calling the <c>CreateOnlyFactory</c> method, which returns an
    /// instance of <c>IClassFactory</c> that only exposes create type methods.</p>
    /// <p>The framework's <c>Application</c> class has a <c>factories</c> property that is uses to expose
    /// a map of different class factories defined in the application configuration file.</p>
    /// </remarks>
    /// <author>matthewd</author>
    public class ClassFactory : MarshalByRefObject
    {
        protected IDictionary m_classRegistryByNickName;
        protected IClassFactory m_createOnly;
        protected string m_factoryName;

        public ClassFactory()
            : this("<<Un-named factory>>")
        {
        }
        /// <summary>Creates a new instance of ClassFactory 
        /// </summary>
        public ClassFactory(string factoryName)
        {
            m_factoryName = factoryName;
            m_classRegistryByNickName = new Hashtable(new CaseInsensitiveHashCodeProvider(), new CaseInsensitiveComparer());
            m_createOnly = new CreateOnlyFactoryImpl(this);
        }

        public string Name { get { return m_factoryName; } }

        public IDictionary entries
        {
            get { return new ReadOnlyDictionary(m_classRegistryByNickName); }
        }

        public IClassFactory CreateOnlyFactory { get { return m_createOnly; } }

        public virtual DotNetClassInfo add(string fullyQualifiedClassName, string uniqueName)
        {
            return add(fullyQualifiedClassName, uniqueName, null, true, null);
        }


        /// <summary>Adds a record to the pseudo-database that holds info about a .NET class
        /// that will be used to implement the field values.
        /// </summary>
        /// <param name="fullname">the fully-qualified name of the .NET class (e.g., <c>"System.String"</c>).
        /// Must not be <c>null</c>.
        /// </param>
        /// <param name="nickname">the nickname of the .NET class (e.g., <c>"String"</c>).
        /// If <c>null</c>, the last component of the <c>fullname</c> is used as the nickname.
        /// E.g., for fullname <c>"System.String"</c>, nickname would be <c>"String"</c>.
        /// If provided, must match the pattern <c>"{identifier}"</c>.
        /// </param>
        /// <param name="ctorparam">the parameter to be passed to constructor of the .NET class to create a sample object.
        /// If <c>null</c>, the default ctor is used; if non-<c>null</c>, the parameter is
        /// passed to the single-parameter ctor.
        /// </param>
        /// <param name="mutable"><c>True</c> if the .NET class has public methods that 
        /// mutate the object's state. (Note: this is used to make <c>BusinessObject.clone()</c> a lot more efficient.)
        /// </param>
        /// <param name="parsable"><c>True</c> if the class supports a static <c>Parse</c> method that
        /// takes a single string param.  Many of the fundamental .NET framework classes support such a method.</param>
        /// <returns><c>null</c> on success; otherwise returns a human-readable error message
        /// that describes what went wrong.
        /// </returns>
		public DotNetClassInfo add(string fullname, string nickname, object ctorparam, bool mutable, string staticFactoryMethod)
		{
			return add(fullname, nickname, ctorparam, mutable, staticFactoryMethod, false);
		}
        public DotNetClassInfo add(string fullname, string nickname, object ctorparam, bool mutable, string staticFactoryMethod, bool isBasic)
        {
            if (nickname == null)
            {
                int cpos = fullname.IndexOf(",");
                if (cpos == -1)
                    nickname = fullname.Substring(fullname.LastIndexOf((System.Char)'.') + 1);
                else
                {
                    string cname = fullname.Substring(0, cpos);
                    nickname = cname.Substring(cname.LastIndexOf(".") + 1).Trim();
                }
            }

            /*
            if (m_classRegistryByNickName[nickname] != null)
                throw new Exception(String.Format("Class '{0}' already exists in class factory with nickName {1}",
                    ((DotNetClassInfo)m_classRegistryByNickName[nickname]).fullName, nickname));
            */
            DotNetClassInfo info = new DotNetClassInfo(fullname, nickname, mutable, staticFactoryMethod, ctorparam, isBasic);
            //m_classRegistryByFullName[fullname] = info;
            m_classRegistryByNickName[nickname] = info;
            return info;
        }

        public DotNetClassInfo add(string nickname, object prototype)
        {
            if (m_classRegistryByNickName[nickname] != null)
                throw new Exception(String.Format("Class '{0}' already exists in class factory with nickName {1}",
                    ((DotNetClassInfo)m_classRegistryByNickName[nickname]).fullName, nickname));

            DotNetClassInfo info = new DotNetClassInfo(prototype.GetType().Name, nickname, true, null, prototype, false);
            m_classRegistryByNickName[nickname] = info;
            return info;

        }

        public object createFromPattern(string info, params object[] extraInfo)
        {
            object obj = null;

            foreach (DotNetClassInfo node in m_classRegistryByNickName.Values)
            {
                try
                {
                    Match mr = Application.currentApp.resMgr.matches(node.nickName, info);
                    if (mr.Success && mr.Length == info.Length)
                    {//A match has been found
                        if (mr.Groups.Count > 1)
                        {
                            //object[] i = new object[extraInfo.Length +1];
                            //i[0] = mr;
                            //Array.Copy(extraInfo,0, i, 1, extraInfo.Length);
                            obj = node.create(mr, extraInfo);
                        }
                        else
                            obj = node.create(extraInfo);
                        break;
                    }
                }
                catch (Exception e)
                {
                    throw new ClassFactoryCreateException(m_factoryName, info, e);
                }
            }

            return obj;
        }

        public DotNetClassInfo classFromNickName(string nickName)
        {
            return (DotNetClassInfo)m_classRegistryByNickName[nickName];
        }

        protected internal class CreateOnlyFactoryImpl : MarshalByRefObject, IClassFactory
        {
            protected internal ClassFactory m_factory;

            public CreateOnlyFactoryImpl(ClassFactory factory)
            {
                m_factory = factory;
            }

            public string Name { get { return m_factory.Name; } }

            public object createFromNickName(string nickName, params object[] createParams)
            {
                try
                {
                    DotNetClassInfo info = m_factory.classFromNickName(nickName);
                    if (info != null)
                        return info.create(createParams);
                }
                catch (Exception e)
                {
                    throw new ClassFactoryCreateException(m_factory.Name, nickName, e, m_factory.entries.Count);
                }

                throw new ClassFactoryCreateException(m_factory.Name, nickName);
            }

            public object createFromPattern(string pattern, params object[] createParams)
            {
                //No need to do exception here as long as the ClassFactory.createFromPattern does good
                //exception handling to throw the ClassFactoryCreateException.
                return m_factory.createFromPattern(pattern, createParams);
            }

            public IDictionary entries
            {
                get { return m_factory.entries; }
            }

        }

    }

    /// <summary>Holds relevant information about a field's underlying .NET class.
    /// </summary>
    public class DotNetClassInfo
    {
        private static ExpirationManager m_expirer;
        private bool m_triedToCreateDefault = false;
        private object m_defaultCtorParam;
		private bool m_isBasic = false;

        public DotNetClassInfo(string fullName, string nickName, bool mutable, string staticFactoryMethod, object ctorParam, bool isBasic)
        {
            m_nickname = nickName;
            m_fullname = fullName;
            m_mutable = mutable;
            m_staticFactoryMethod = staticFactoryMethod;
            m_defaultCtorParam = ctorParam;
			m_isBasic = isBasic;
        }

        public virtual object create(params object[] ctorParams)
        {
            object obj = null;
            object[] newParams = null;
            Type[] types = new Type[ctorParams.Length];
            for (int pos = 0; pos < ctorParams.Length; ++pos)
                if (ctorParams[pos] == null)
                    types[pos] = typeof(System.Object);
                else
                    types[pos] = ctorParams[pos].GetType();

            if (newParams == null)
                newParams = ctorParams;

            if (StaticFactoryMethod != null && StaticFactoryMethod != "")
            {
                if (StaticFactoryMethod == "Parse")
                {
                    if (newParams.Length == 0 || newParams[0] == null)
                    {
                        if (cloneable)
                            obj = ((ICloneable)m_defaultValue).Clone();
                        else if (!m_mutable)
                            obj = m_defaultValue;

                        if (obj == null)
                            obj = Cmn.callCtor(fullName, types, newParams);
                    }
                    else
                    {
						//This is done because calling toString on datetime will clip milliseconds and the
						//newly created date wont match the given date exactly (it would down to the second only).
						//This check avoids that scenario.
						if (m_defaultValue is DateTime && newParams[0] is DateTime)
							obj = newParams[0];
						else
						{
							types[0] = typeof(string);
							newParams[0] = newParams[0].ToString();
						}
                    }
                }

                if (obj == null)
                    obj = Cmn.callStatic(fullName, StaticFactoryMethod, types, newParams);
            }
            else if (newParams.Length == 0)
            {
                if (cloneable)
                    obj = ((ICloneable)m_defaultValue).Clone();
                else if (!m_mutable)
                    obj = m_defaultValue;
                /*
            else if(defaultValue != null)
            {
                types = new Type[] {defaultValue.GetType()};
                newParams = new Object[] {defaultValue};
            }
            */
            }
            else if (fullName == "System.String")
            {
                if (newParams.Length > 0)
                {
                    if (newParams[0] == null)
                        return null; //Out of place to return here, but we need to short-cut things when a string is being created with a null, which means a null should be returned as well.
                    else
                        obj = newParams[0].ToString();
                }
                else obj = "";
            }
            else if (fullName == "System.Object")
            {
                obj = new Object();
            }
            if (obj == null)
                obj = Cmn.callCtor(fullName, types, newParams);

            if (obj is IExpireNode)
            {
                if (m_expirer == null)
                    m_expirer = new ExpirationManager();
                m_expirer.addItem((IExpireNode)obj);
            }

            return obj;
        }

        public void addCustomProperty(string name, string value)
        {
            CustomProperties[name] = value;
        }

		public bool IsBasic { get { return m_isBasic; } set { m_isBasic = value; } }

        /// <summary><c>this.fullname</c> is the fully qualified name of the .NET class described by this <c>DotNetClassInfo</c>.
        /// Example: <c>"System.String"</c>.
        /// </summary>
        protected string m_fullname;
        public string fullName
        {
            get
            {
                return m_fullname;
            }
        }

        private IDictionary m_customProperties;
        public IDictionary CustomProperties
        {
            get
            {
                if (m_customProperties == null)
                    m_customProperties = new Hashtable();
                return m_customProperties;
            }
        }

        /// <summary><c>this.nickname</c> is the nickname of the .NET class described by this <c>DotNetClassInfo</c>.
        /// Example: <c>"String"</c>.
        /// </summary>
        protected string m_nickname;
        public string nickName
        {
            get
            {
                return m_nickname;
            }
        }

        /// <summary><c>this.defaultValue</c> is a sample object of the .NET class described by this <c>DotNetClassInfo</c>.
        /// Example: if <c>this.fullname</c> is <c>"String"</c>, <c>this.defaultValue</c> might be <c>""</c>.
        /// </summary>
        protected object m_defaultValue;
        public object defaultValue
        {
            get
            {
                if (!m_triedToCreateDefault)
                {
                    // Create a sample value for this field-type.
                    try
                    {
                        if (!m_mutable)
                        {
                            if (String.Compare("system.string", m_fullname, true) != 0)
                            {
                                System.Type[] types = (m_defaultCtorParam == null ? new System.Type[] { } : new System.Type[] { m_defaultCtorParam.GetType() });
                                object[] callParams = (m_defaultCtorParam == null ? new object[] { } : new object[] { m_defaultCtorParam });
                                m_defaultValue = Cmn.callCtor(m_fullname, types, callParams);
                            }
                            else if (m_defaultCtorParam == null)
                                m_defaultValue = "";
                            else
                                m_defaultValue = m_defaultCtorParam.ToString();
                        }
                        else
                        {
                            System.Type[] types = (m_defaultCtorParam == null ? new System.Type[] { } : new System.Type[] { m_defaultCtorParam.GetType() });
                            object[] callParams = (m_defaultCtorParam == null ? new object[] { } : new object[] { m_defaultCtorParam });
                            m_defaultValue = Cmn.callCtor(m_fullname, types, callParams);
                        }
                    }
                    catch (Exception)
                    {
                        //Its ok if there isn't a default value
                    }
                }

                return m_defaultValue;
            }
            set
            {
                System.Type[] types = new System.Type[] { value.GetType() };
                object[] callParams = new object[] { value };
                m_defaultValue = Cmn.callCtor(m_fullname, types, callParams);
                m_triedToCreateDefault = true;
            }
        }

        /// <summary><c>this.cloneable</c> is <c>true</c> if and only if the class described by this <c>DotNetClassInfo</c> has
        /// a <c>clone()</c> method that can be called via <c>Cmn.callMethod(...)</c>.
        /// This is conceptually similar to saying <c>if (this.sampleObject() implements Cloneable) ...</c>,
        /// but saying <c>if (this.cloneable) ...</c> is more practical (there might be a class
        /// that has a public <c>clone()</c> method yet forgets to say <c>implements Cloneable</c>).
        /// </summary>
        public bool cloneable
        {
            get
            {
                bool isIt = false;
                if (defaultValue != null)
                    isIt = (defaultValue is ICloneable);
                return isIt;
            }
        }

        protected string m_staticFactoryMethod;
        public string StaticFactoryMethod { get { return m_staticFactoryMethod; } }

        /// <summary>
        /// <c>this.mutable</c> is <c>true</c> if and only if the objects of the .NET class described by
        /// this <c>DotNetClassInfo</c> are mutable.</summary>
        /// <remarks>
        /// <p>For example, .NET's <c>String</c> class produces objects that are
        /// immutable: it is impossible to modify a <c>String</c> object after it is
        /// constructed (you can change your <c>String</c> <em>pointer</em> so it points
        /// at a different <c>String</c> object, but you cannot modify the
        /// <c>String</c> <em>object</em>).
        /// </p><p>
        /// This information enables an important performance improvement on
        /// <c>BusinessObject.clone()</c> and <c>BusinessObjects.create()</c>.
        /// It turns out that most small fields (such as strings and numbers) are
        /// logically the exclusive property of their containing <c>BusinessObject</c>,
        /// meaning logically we ought to create a fresh field object every time
        /// their containing <c>BusinessObject</c> is created and/or cloned (if
        /// two different people have the same identical name, for example there
        /// could be aliasing problems if one of them changes their name).
        /// However it also turns out that most small fields (such as strings
        /// and numbers) are immutable, meaning it's totally unnecessary to create
        /// a fresh field object: the two <c>BusinessObjects</c> can alias the
        /// same identical field object since neither can mutate the field object.
        /// </p><p>
        /// So <c>BusinessObject.create()</c> and <c>BusinessObject.clone()</c>
        /// never deep-copy a field object of an immutable type, which substantially
        /// improves performance.</p></remarks>
        public bool mutable
        {
            get
            {
                return m_mutable;
            }
        }
        private bool m_mutable;

    }

}